﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Data;
using System.Data.EntityClient;
using System.Data.SqlServerCe;
using System.Data.Objects;
using System.Diagnostics;

namespace Net.Chirash.Twitter.TwitterDatabase
{
    public class TwitterCaching
        : IDisposable
    {
        private TwitterCachingConnection conn;

        public TwitterCaching(TwitterCachingConnection conn)
        {
            this.conn = conn;
            conn.Open();
        }

        public ICollection<HashTag> CreateHashTag(Status sta)
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder(160);
            sb.Append(' ');
            sb.Append(sta.Text.Replace(" ", "  "));
            sb.Append(' ');

            var list = new List<HashTag>();
            foreach (System.Text.RegularExpressions.Match m in System.Text.RegularExpressions.Regex.Matches(sb.ToString(), @"\s#(.*?)\s"))
            {
                var tagid = createHashTagID(m.Value);

                list.Add(new HashTag()
                {
                    Tag = m.Value,
                    TagID = tagid,
                });
            }

            return list;
        }

        private Guid createHashTagID(string str)
        {
            var md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
            return new Guid(md5.ComputeHash(System.Text.Encoding.Unicode.GetBytes(str)));
        }

        #region Update
        public void UpdateStatus(Status sta)
        {
            UpdateStatuses(new Status[] { sta });
        }
        public void UpdateStatuses(IEnumerable<Status> stas)
        {
            UseEntity((context) =>
            {
                internalUpdateUser(stas.Select((s) => s.User).Distinct(), false);

                foreach (var sta in stas)
                {
                    if (sta == null)
                        continue;

                    var attached = context.AddOrAttachInstance(sta, true);
                    ChangeStateAddedOrModified(context, attached, () => context.Statuses.Any((s) => s.StatusID == sta.StatusID));
                }
                //context.AttachObjectGraphs<Status>(stas.Where((s) => s != null), (s) => s.User);
                   context.SaveChanges(SaveOptions.AcceptAllChangesAfterSave | SaveOptions.DetectChangesBeforeSave);
             //context.Statuses.MergeOption = MergeOption.AppendOnly;
                //foreach (var sta in stas)
                //    context.Statuses.Detach(sta);
            }, true, IsolationLevel.Serializable);
        }

        #region UpdateUser
        public void UpdateUser(User user)
        {
            this.UpdateUsers(new User[] { user });
        }

        public void UpdateUsers(IEnumerable<User> users)
        {
            internalUpdateUser(users, true);
        }

        protected void internalUpdateUser(IEnumerable<User> users, bool setTransaction)
        {
            UseEntity((context) =>
                {

                    foreach (var user in users)
                    {
                        if (user == null)
                            continue;

                        var attached = context.AddOrAttachInstance(user, true);
                        ChangeStateAddedOrModified(context, attached, () => context.Users.Any((u) => u.UserID == user.UserID));
                    }
                    //context.AttachObjectGraphs<User>(users.Where((user) => user != null).Distinct(),
                    //    (u) => u.Statuses,
                    //    (u) => u.SentDirectMessages,
                    //    (u) => u.ReceivedDirectMessages,
                    //    (u) => u.OwnedTwitterLists,
                    //    (u) => u.MemberedTwitterLists,
                    //    (u) => u.Friends,
                    //    (u) => u.Followers,
                    //    (u) => u.SubscribingTwitterLists,
                    //    (u) => u.RepliedUsers
                    //                                 );

                    //context.Users.MergeOption = MergeOption.AppendOnly;
                    context.SaveChanges(SaveOptions.AcceptAllChangesAfterSave | SaveOptions.DetectChangesBeforeSave);

                    //foreach (var user in users)
                    //{
                    //    context.Users.Detach(user);
                    //}
                }, setTransaction, IsolationLevel.Serializable);

        }
        #endregion

        public void UpdateUserFriends(long userid, IEnumerable<User> friends)
        {
            //TODO: 直接操作する
            internalUpdateUserFriends(userid, friends, true);
        }

        protected void internalUpdateUserFriends(long userid, IEnumerable<User> friends, bool transaction)
        {
            if (friends == null || !friends.Any())
                return;

            UseEntity((context) =>
                {
                    foreach (var friend in friends)
                    {
                        if (!this.internalExistUserFriend(userid, friend.UserID, false))
                            internalUpdateFriendFollower(friend.UserID, userid, false);
                    }

                }, transaction, IsolationLevel.Serializable);
        }

        public void UpdateUserFollowers(long userid, IEnumerable<User> followers)
        {
            //TODO: 直接操作する
            internalUpdateUserFollowers(userid, followers, true);
        }

        protected void internalUpdateUserFollowers(long userid, IEnumerable<User> followers, bool transaction)
        {
            if (followers == null || !followers.Any())
                return;

            UseEntity((context) =>
                {
                    foreach (var follower in followers)
                    {
                        if (!this.internalExistUserFollower(userid, follower.UserID, false))
                            internalUpdateFriendFollower(userid, follower.UserID, false);
                    }

                }, transaction, IsolationLevel.Serializable);
        }

        private void internalUpdateFriendFollower(long friend, long follower, bool transaction)
        {
            UseEntity((context) =>
                {
                    var cmdstr = @"insert into fk_Friends_Followers ( Friends_UserID , Followers_UserID ) values ({0}, {1})";
                    context.ExecuteStoreCommand(cmdstr, friend, follower);

                }, transaction, IsolationLevel.Serializable);
        }

        public void UpdateDirectMessage(DirectMessage directmessage)
        {
            this.UpdateDirectMessages(new DirectMessage[] { directmessage });
        }
        public void UpdateDirectMessages(IEnumerable<DirectMessage> directmessages)
        {
            internalUpdateDirectMessages(directmessages, true);
        }

        protected void internalUpdateDirectMessages(IEnumerable<DirectMessage> directmessages, bool transaction)
        {
            UseEntity((context) =>
                {
                    var userlist = new List<User>();
                    foreach (var dm in directmessages)
                    {
                        if (dm.Recipient != null && !userlist.Contains(dm.Recipient))
                            userlist.Add(dm.Recipient);
                        if (dm.Sender != null && !userlist.Contains(dm.Sender))
                            userlist.Add(dm.Sender);
                    }
                    internalUpdateUser(userlist, false);

                    foreach (var dm in directmessages)
                    {
                        dm.Sender = null;
                        dm.Recipient = null;

                        if (internalExistDirectMessage(dm.DirectMessageID, false))
                        {
                            try
                            {
                                context.DirectMessages.Attach(dm);
                            }
                            catch (InvalidOperationException) { }
                        }
                        else
                        {
                            context.DirectMessages.AddObject(dm);
                        }
                    }

                    context.SaveChanges(SaveOptions.AcceptAllChangesAfterSave | SaveOptions.DetectChangesBeforeSave);
                    //foreach (var dm in directmessages)
                    //    context.DirectMessages.Detach(dm);

                }, transaction, IsolationLevel.Serializable);
        }
        #endregion

        #region helper

        protected void UseEntity(Action<TwitterEntities> act, bool useTransaction, IsolationLevel isolation)
        {
            if (useTransaction)
            {
                //sqlceはcommandをスレッド間で共有できないので、isolationはserializable
                lock (conn.LockObjectForEntities)
                {
                    var sf = new StackFrame(1);
                    Console.WriteLine("Hashcode = {0}; ThreadID = {1}; {2} - UseEntity() entered", this.GetHashCode(), System.Threading.Thread.CurrentThread.ManagedThreadId, sf.GetMethod().Name);
                    using (var ts = conn.EntityConnection.StoreConnection.BeginTransaction(IsolationLevel.Serializable))
                    {
                        try
                        {
                            act(this.conn.Entities);
                            ts.Commit();
                        }
                        catch (EntityException)
                        {
                            ts.Rollback();
                        }
                    }
                    Console.WriteLine("Hashcode = {0}; ThreadID = {1}; UseEntity() leaved", this.GetHashCode(), System.Threading.Thread.CurrentThread.ManagedThreadId);
                }
            }
            else
            {
                act(this.conn.Entities);
            }
        }

        protected void SetMergeOption<T>(Action act, ObjectSet<T> ob, MergeOption option)
            where T : class
        {
            var before = ob.MergeOption;
            ob.MergeOption = option;
            act();
            ob.MergeOption = before;
        }

        /// <summary>
        /// オブジェクトステート値の切り替え。データベースに値が存在しているかどうかでAddedかModifiedを切り替えます。
        /// </summary>
        protected void ChangeStateAddedOrModified(ObjectContext context, object entity, Func<bool> pred)
        {
            var state = context.ObjectStateManager.GetObjectStateEntry(entity);
            if (pred())
                state.ChangeState(EntityState.Modified);
            else
                state.ChangeState(EntityState.Added);
        }

        #endregion

        #region Exist

        public bool ExistUserFriend(User targetUser, User friend)
        {
            return internalExistUserFriend(targetUser.UserID, friend.UserID, true);
        }

        protected bool internalExistUserFriend(long targetUserID, long friendUserID, bool transaction)
        {
            //return internalExistFriendFollower(friendUserID, targetUserID, transaction);
            bool result = false;
            UseEntity((context) =>
                {
                    var q = from currentUser in context.Users
                            from friend in context.Users
                            where currentUser.UserID == targetUserID
                                && currentUser.Friends.Contains(friend)
                                && friend.UserID == friendUserID
                            select friend;
                    result = q.Any();
                }, transaction, IsolationLevel.ReadCommitted);
            return result;
        }

        public bool ExistUserFollower(User targetUser, User follower)
        {
            return internalExistUserFollower(targetUser.UserID, follower.UserID, true);
        }

        protected bool internalExistUserFollower(long targetUserID, long followerUserID, bool transaction)
        {
            //return internalExistFriendFollower(targetUserID, followerUserID, transaction);
            bool result = false;
            UseEntity((context) =>
            {
                var q = from currentUser in context.Users
                        from follower in context.Users
                        where currentUser.UserID == targetUserID
                            && currentUser.Followers.Contains(follower)
                            && follower.UserID == followerUserID
                        select follower;
                result = q.Any();
            }, transaction, IsolationLevel.ReadCommitted);
            return result;

        }

        private bool internalExistFriendFollower(long friend, long follower, bool transaction)
        {
            bool result = false;
            UseEntity((context) =>
            {
                string cmdstr = @"select count(*) from fk_Friends_Followers where Friends_UserID = @follower and Followers_UserID = @follower";
                var cmd = new SqlCeCommand(cmdstr, (context.Connection as EntityConnection).StoreConnection as SqlCeConnection);

                var friendparam = new SqlCeParameter("@follower", SqlDbType.BigInt);
                friendparam.Value = friend;
                var followerparam = new SqlCeParameter("@follower", SqlDbType.BigInt);
                followerparam.Value = follower;

                cmd.Parameters.Add(friendparam);
                cmd.Parameters.Add(followerparam);
                int scalar = (int)cmd.ExecuteScalar();
                result = scalar > 0;
            }, transaction, IsolationLevel.ReadCommitted);
            return result;

        }

        public bool ExistUser(long userid)
        {
            return internalExistUser(userid, true);
        }
        protected bool internalExistUser(long userid, bool transaction)
        {
            bool result = false;
            UseEntity((context) =>
            {
                result = context.Users.Any((user) => user.UserID == userid);

            }, transaction, IsolationLevel.ReadCommitted);
            return result;
        }

        public bool ExistStatus(long statusId)
        {
            return internalExistStatus(statusId, true);
        }

        protected bool internalExistStatus(long statusId, bool transaction)
        {
            bool result = false;
            UseEntity((context) =>
            {
                result = context.Statuses.Any((sta) => sta.StatusID == statusId);

            }, transaction, IsolationLevel.ReadCommitted);
            return result;
        }

        public bool ExistDirectMessage(long directmessageId)
        {
            return internalExistDirectMessage(directmessageId, true);
        }
        protected bool internalExistDirectMessage(long directmessageId, bool transaction)
        {
            bool result = false;
            UseEntity((context) =>
            {
                result = context.DirectMessages.Any((dm) => dm.DirectMessageID == directmessageId);

            }, transaction, IsolationLevel.ReadCommitted);
            return result;
        }

        //protected bool internalExistHashTag(string text)
        //{
        //    bool result = false;
        //    UseSqlCe(() =>
        //        {

        //        }, false, IsolationLevel.ReadCommitted);
        //    return false;
        //}

        #endregion

        #region Get

        public IEnumerable<Status> GetAllStatus()
        {
            return internalGetAllStatus(true);
        }

        protected IEnumerable<Status> internalGetAllStatus(bool transaction)
        {
            IEnumerable<Status> result = new Status[0];

            UseEntity((context) =>
            {
                SetMergeOption<Status>(() =>
                {
                    result = from e in context.Statuses select e;

                }, context.Statuses, MergeOption.NoTracking);

            }, transaction, IsolationLevel.ReadCommitted);

            return result;
        }

        public User GetUser(long userid)
        {
            return internalGetUser(userid, true);
        }

        protected User internalGetUser(long userid, bool transaction)
        {
            User result = null;
            UseEntity((context) =>
                {
                    SetMergeOption<User>(() =>
                    {
                        var q = from u in context.Users where u.UserID == userid select u;
                        if (q != null && q.Any())
                            result = q.First();

                    }, context.Users, MergeOption.NoTracking);

                }, transaction, IsolationLevel.ReadCommitted);
            return result;

        }

        public Status GetStatus(long statusid)
        {
            return internalGetStatus(statusid, true);
        }

        protected Status internalGetStatus(long statusid, bool transaction)
        {
            Status result = null;
            UseEntity((context) =>
            {
                SetMergeOption<Status>(() =>
                {
                    var q = from sta in context.Statuses.Include("User")
                            where sta.StatusID == statusid
                            select sta;
                    if (q != null && q.Any())
                        result = q.First();

                }, context.Statuses, MergeOption.NoTracking);

            }, transaction, IsolationLevel.ReadCommitted);
            return result;
        }

        public DirectMessage GetDirectMessage(long directmessageId)
        {
            return internalGetDirectMessage(directmessageId, true);
        }
        protected DirectMessage internalGetDirectMessage(long directmessageId, bool transaction)
        {
            DirectMessage result = null;
            UseEntity((context) =>
            {
                SetMergeOption<DirectMessage>(() =>
                {
                    var q = from dm in context.DirectMessages where dm.DirectMessageID == directmessageId select dm;
                    if (q != null && q.Any())
                        result = q.First();

                }, context.DirectMessages, MergeOption.NoTracking);

            }, transaction, IsolationLevel.ReadCommitted);
            return result;
        }

        #endregion

        #region Destroy

        public void DestroyStatus(long staid)
        {
            internalDestroyStatus(staid, true);
        }
        protected void internalDestroyStatus(long staid, bool transaction)
        {
            UseEntity((context) =>
            {
                if (internalExistStatus(staid, false))
                {
                    var sta = new Status { StatusID = staid };
                    var attached = context.AddOrAttachInstance(sta, true);
                    context.Statuses.DeleteObject((Status)attached);
                    context.SaveChanges();
                }
            }, transaction, IsolationLevel.Serializable);
        }

        public void DestroyDirectMessage(long dmid)
        {
            internalDestroyDirectMessage(dmid, true);
        }
        protected void internalDestroyDirectMessage(long dmid, bool transaction)
        {
            UseEntity((context) =>
                {
                    if (internalExistDirectMessage(dmid, false))
                    {
                        var dm = new DirectMessage() { DirectMessageID = dmid };
                        try
                        {
                            context.DirectMessages.Attach(dm);
                        }
                        catch (InvalidOperationException) { }

                        context.DirectMessages.DeleteObject(dm);
                        context.SaveChanges();
                    }

                }, transaction, IsolationLevel.Serializable);
        }

        #endregion

        #region Dispose-Finalize Pattern

        private bool __disposed = false;
        public void Dispose()
        {
            GC.SuppressFinalize(this);
            this.Dispose(true);
        }

        protected void Dispose(bool disposing)
        {
            if (__disposed) return;
            if (disposing)
            {

            }

            conn.Close();

            //base.Dispose();
            __disposed = true;
        }

        ~TwitterCaching()
        {
            this.Dispose(false);
        }
        #endregion

    }
}
